import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.simple.JSONObject;

public class Parser {

	private static final String BASE_METHOD_SIG = "(\\s)*(public|private|protected)?(\\s)*[0-9a-zA-Z\\[\\]<>]+(\\s)+[0-9a-zA-Z\\[\\]<>]*(\\s)*[0-9a-zA-Z]+(\\s)*\\((\\s*[0-9a-zA-Z<>\\[\\],\\.])*(\\s)*";
	private Stack<TokenValuePair> tokenStack = new Stack<TokenValuePair>();

	public Parser() {

	}

	/*	Creating a KeyValue Object 

		token is one of IfS or LoopS
		value is the number of open curly braces yet to be closed
	 */
	private class TokenValuePair {
		private ExprS token;
		private int value;

		private TokenValuePair(ExprS s, int i){
			this.token = s;
			this.value = i;
		}

		private ExprS getToken() {
			return this.token;
		}

		private int getValue() {
			return this.value;
		}

		private void setValue(int i){
			this.value = i;
		}
	}

	/**
	 * Reads in a java file and creates an AbstractAndroidClass object with a list of
	 * AbstractAndroidMethods that represent the methods within the class
	 * @param file is any java file
	 * @return AbstractAndroidClass is an object that represent the file's class
	 */
	public AbstractAndroidClass parse (File file) {

		AbstractAndroidClass androidClass = new AbstractAndroidClass(file.getName());
		try {
			BufferedReader br = new BufferedReader(new FileReader(file));
			String line;
			while((line = br.readLine()) != null) {
				if(checkMethodSignature(line)) {
					String methodName = parseMethodName(line);
					AbstractAndroidMethod androidMethod = new AbstractAndroidMethod(methodName);
					androidMethod.setAstList(createASTList(line, br));
					androidClass.addAbstractAndroidMethod(androidMethod);
				}
				else if(!line.matches("\\s+") ){
					AbstractNonMethod nonMethod = new AbstractNonMethod(line);
					androidClass.addAbstractNonMethod(nonMethod);
				}
			}
			br.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return androidClass;
	}

	/**
	 * Checks the line and returns true if it contains a method signature,
	 * false otherwise
	 * @param line is the specified string
	 * @return Boolean
	 */
	private Boolean checkMethodSignature(String line) {
		if(line.contains("throws")) {
			String newLine = line.substring(0, line.indexOf("throws"));
			if(line.contains("{")) {
				newLine = newLine + "{";
			}
			line = newLine;
		}
		String methodSigClosingParenthesis = BASE_METHOD_SIG + "((\\s)*[0-9a-zA-Z,])*\\)((\\s)*[0-9a-zA-Z,]*)*";
		String methodSigClosingCurl = methodSigClosingParenthesis + "\\{(\\s)*";
		if(line.matches(BASE_METHOD_SIG) || line.matches(methodSigClosingParenthesis) || line.matches(methodSigClosingCurl)) {
			return true;
		}
		return false;
	}

	/**
	 * Check to see if the string contains an if declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains an if statement, false otherwise
	 */
	private Boolean checkIfStatement(String line) {
		String ifToken = "(\\s|\t)*if(\\s)*\\(.*";
		if(line.matches(ifToken)){
			return true;
		}
		return false;
	}
	
	/**
	 * Check to see if the string contains an if declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains an if statement, false otherwise
	 */
	private Boolean checkElse(String line) {
		String elseToken = "(\\s|\t)*else(\\s)+(if)*.*";
		if(line.matches(elseToken)){
			return true;
		}
		return false;
	}


	/**
	 * Check to see if the string contains an else if declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains an else if statement, false otherwise
	 */
	private Boolean checkElseIf(String line) {
		String elseToken = "(\\s|\t)*else(\\s)+(if).*";
		if(line.matches(elseToken)){
			return true;
		}
		return false;
	}
	

	/**
	 * Check to see if the string contains a loop declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains a for or while loop, false otherwise
	 */
	private Boolean checkLoop(String line) {
		String forToken = "(\\s|\t)*for(\\s)*\\(.*";
		String whileToken = "(\\s|\t)*while(\\s)*\\(.*";
		if(line.matches(forToken)||line.matches(whileToken)){
			return true;
		}
		return false;
	}

	/**
	 * Check to see if the string contains a for loop declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains a for loop, false otherwise
	 */
	private Boolean checkForLoop(String line) {
		String forToken = "(\\s|\t)*for(\\s)*\\(.*";
		if(line.matches(forToken)){
			return true;
		}
		return false;
	}


	/**
	 * Check to see if the string contains a while loop declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains a while loop, false otherwise
	 */
	private Boolean checkWhileLoop(String line) {
		String whileToken = "(\\s|\t)*while(\\s)*\\(.*";
		if(line.matches(whileToken)){
			return true;
		}
		return false;
	}


	/**
	 * Check to see if the string contains a do while loop declaration
	 * @param line is the specified string
	 * @return Boolean, true if it contains a do while loop, false otherwise
	 */
	private Boolean checkDoWhileLoop(String line) {
		String doToken = "(\\s|\t)*do(\\s)*\\{.*";
		if(line.matches(doToken)){
			return true;
		}
		return false;
	}

	/**
	 * Given a string that contains a method declaration, returns a string
	 * that matches the method's name
	 * @Precondition: checkMethodSignature(line) is true
	 * @param line is the specified string
	 * @return a string that matches the method's name
	 */
	public String parseMethodName(String line) {

		Pattern pattern = Pattern.compile("(\\s)[0-9a-zA-Z]+(\\s)*\\(");
		Matcher matcher = pattern.matcher(line);

		if(matcher.find()){	
			String methodName = matcher.group();
			methodName = methodName.substring(1, methodName.length()-1);
			if(methodName.contains(" ")){
				int whiteSpace = methodName.indexOf(' ');
				return methodName.substring(0, whiteSpace);
			}
			else
				return methodName;
		}
		return null;
	}

	/**
	 * Creates a list of ExprS objects that represent a method's Abstract
	 * Syntax Tree. Each element in the returned list is either an IfS that
	 * represents an if statement, LoopS that represents a for or while loop,
	 * or ExprS that represent any other line. Each of these objects also
	 * has a list of each other that represent nested ifs or loops. The method
	 * terminates once the end of the method is reached which is represented by the
	 * closing curly brace.
	 * @param line is the specified string that starts after the first opening curly brace
	 * @param br is a BufferedReader that reads the next line
	 * @return a list of ExprS objects that represent a method's AST
	 */
	private List<ExprS> createASTList(String line, BufferedReader br){
		int count = 0;
		ArrayList<ExprS> astList = new ArrayList<ExprS>();

		try {
			while(count == 0){
				if(line.contains("{")){
					count++;
				}
				else
					line = br.readLine();
			}
			mainloop:
			while(count >= 1 && (line = br.readLine()) != null){
				if(tokenStack.isEmpty()){
					if(checkIfStatement(line)){
						createIfAST(line,br,astList);

					} else if(checkLoop(line)){	
						createLoopAST(line,br);

					} else if(checkDoWhileLoop(line)){
						List<ExprS> exprS = new ArrayList<ExprS>();
						LoopS doLoop = new LoopS("", exprS);
						TokenValuePair doPair = new TokenValuePair(doLoop,1);
						tokenStack.push(doPair);
					} else {
						for(int i = 0; i<line.length(); i++) {
							if(line.charAt(i) == '{') {
								count++;
							}
							if(line.charAt(i) == '}') {
								count--;
							}
						}
						if(count == 0) {
							break;
						}
						if(astList.isEmpty()) {
							ExprS exprS = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
							astList.add(exprS);
						}
						else if(astList.get(astList.size()-1) instanceof IfS || astList.get(astList.size()-1) instanceof LoopS){
							ExprS exprS = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
							astList.add(exprS);
						} else {
							ExprS exprS = new ExprS(astList.get(astList.size()-1).getCode() + line.replaceFirst("(\\s|\t)+", ""));
							astList.set(astList.size()-1, exprS);
						}
					}
				} else if (tokenStack.peek().getToken() instanceof IfS) {
					TokenValuePair head = tokenStack.peek();
					if(checkIfStatement(line)) {
						createIfAST(line,br,astList);
					} else if(checkLoop(line)) {
						createLoopAST(line,br);
					} else {
						for(int i = 0; i<line.length(); i++) {
							if(line.charAt(i) == '{') {
								head.setValue(head.getValue()+1);
							}
							if(line.charAt(i) == '}') {
								head.setValue(head.getValue()-1);
							}
							if(head.getValue() == 0) {
								TokenValuePair temp = tokenStack.pop();
								if(tokenStack.isEmpty()){
									astList.add(temp.getToken());
								} else {
									if(tokenStack.peek().getToken() instanceof IfS) {
										((IfS) tokenStack.peek().getToken()).getThen().add(temp.getToken());
									}
									if(tokenStack.peek().getToken() instanceof LoopS) {
										((LoopS) tokenStack.peek().getToken()).getBody().add(temp.getToken());
									}
								}
								if(line.length()-1 != i) {
									String afterBrace = line.substring(i+1, line.length());
									if(checkElse(afterBrace)) {
										if(checkElseIf(afterBrace)) {
											createIfAST(afterBrace, br, astList);
											continue mainloop;
										} else {
											List<ExprS> exprS = new ArrayList<ExprS>();
											IfS elseS = new IfS("", exprS);
											TokenValuePair elsePair = new TokenValuePair(elseS, 1);
											tokenStack.push(elsePair);
											continue mainloop;
										}
									}
								}
								else {
									continue mainloop;
								}
								break;
							}
						} 
						List<ExprS> ast = ((IfS)head.getToken()).getThen();
						if(ast.isEmpty()) {
							ExprS exprS = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
							ast.add(exprS);
						}
						else if(ast.get(ast.size()-1) instanceof IfS || ast.get(ast.size()-1) instanceof LoopS){
							ExprS exprS = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
							ast.add(exprS);
						} else {
							ExprS exprS = new ExprS(ast.get(ast.size()-1).getCode() + line.replaceFirst("(\\s|\t)+", ""));
							ast.set(ast.size()-1, exprS);
						}
					}

				} else if(tokenStack.peek().getToken() instanceof LoopS) {
					TokenValuePair head = tokenStack.peek();
					if(checkIfStatement(line)) {
						createIfAST(line,br,astList);
					} else if(checkLoop(line)) {
						createLoopAST(line,br);
					} else {
						for(int i = 0; i<line.length(); i++) {
							if(line.charAt(i) == '{') {
								head.setValue(head.getValue()+1);
							}
							if(line.charAt(i) == '}') {
								head.setValue(head.getValue()-1);
							}
							if(head.getValue() == 0) {
								TokenValuePair temp = tokenStack.pop();
								if(((LoopS) temp.getToken()).getCode().equals("")) {
									String doCond = findCondition(line, br, "while(\\s)*\\(");
									temp.getToken().setCode(doCond.substring(0,doCond.lastIndexOf(")")));
								}
								if(tokenStack.isEmpty()){
									astList.add(temp.getToken());
								} else {
									if(tokenStack.peek().getToken() instanceof IfS) {
										((IfS) tokenStack.peek().getToken()).getThen().add(temp.getToken());
									}
									if(tokenStack.peek().getToken() instanceof LoopS) {
										((LoopS) tokenStack.peek().getToken()).getBody().add(temp.getToken());
									}
								}
								continue mainloop;
							}
						} 
						List<ExprS> ast = ((LoopS)head.getToken()).getBody();
						if(ast.isEmpty()) {
							ExprS exprS = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
							ast.add(exprS);
						}
						else if(ast.get(ast.size()-1) instanceof IfS || ast.get(ast.size()-1) instanceof LoopS){
							ExprS exprS = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
							ast.add(exprS);

						} else {
							ExprS exprS = new ExprS(ast.get(ast.size()-1).getCode() + line.replaceFirst("(\\s|\t)+", ""));
							ast.set(ast.size()-1, exprS);
						}
					}
				}
			}
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		return astList;
	}

	/**
	 * Creates an IfS object given a line that contains an if declaration
	 * the IfS object is then pushed on to the class's tokenStack variable
	 * if the body is more than one line, otherwise it is added to the astList
	 * @Precondition: checkIfStatement(line) is true
	 * @param line is the specified string
	 * @param br reads the next line of the file
	 * @param astList is the astList passed in by createASTList(String,BufferedReader)
	 */
	private void createIfAST(String line, BufferedReader br, List<ExprS> astList) {

		List<ExprS> ifBody = new ArrayList<ExprS>();
		String ifCond = findCondition(line, br, "if(\\s)*\\(");
		IfS ifTree = new IfS(ifCond.substring(0, ifCond.lastIndexOf(")")), ifBody);
		String afterParen = ifCond.substring(ifCond.lastIndexOf(")"),ifCond.length());
		if(afterParen.contains("{")) {
			TokenValuePair ifPair = new TokenValuePair(ifTree,1);
			tokenStack.push(ifPair);
		}
		else if(ifCond.lastIndexOf(")") < ifCond.length()-1){
			String afterParen2 = afterParen.substring(1,afterParen.length()-1);
			ExprS ifExprs = new ExprS(afterParen2.replaceFirst("(\\s|\t)+", ""));
			ifBody.add(ifExprs);
			ifTree.setThen(ifBody);
			astList.add(ifTree);
		}
		else if(ifCond.lastIndexOf(")") == ifCond.length()-1){
			try {
				line = br.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			ExprS ifExprs = new ExprS(line.replaceFirst("(\\s|\t)+", ""));
			ifBody.add(ifExprs);
			ifTree.setThen(ifBody);
			astList.add(ifTree);
		}
	}

	/**
	 * Creates an LoopS object given a line that contains an if declaration
	 * the LoopS object is then pushed on to the class's tokenStack variable
	 * @Precondition: checkLoop(line) is true
	 * @param line is the specified string
	 * @param br reads the next line of the file
	 * */
	private void createLoopAST(String line, BufferedReader br) {
		List<ExprS> loopBody = new ArrayList<ExprS>();
		String loopCond = "";
		if(checkForLoop(line)) {
			loopCond = findCondition(line, br, "for(\\s)*\\(");
		}
		if(checkWhileLoop(line)) {
			loopCond = findCondition(line, br, "while(\\s)*\\(");
		}
		LoopS loopTree = new LoopS(loopCond.substring(0, loopCond.lastIndexOf(")")), loopBody);
		TokenValuePair loopPair = new TokenValuePair(loopTree,1);
		tokenStack.push(loopPair);

	}

	/**
	 * Given a String that contains an if or loop declaration, the method
	 * finds the condition contained in the parentheses after the if, for, or
	 * while token. The opening parentheses is found after the matched regex
	 * parameter.
	 * @Precondition: checkIfStatement(line) or checkLoop(line) is true
	 * @param line is the specified string
	 * @param br reads the next line in the file
	 * @param regex is a valid regex that finds the if, for or while token
	 * @return a string that represent the condition of an if or loop delcaration
	 */
	private String findCondition(String line, BufferedReader br, String regex) {

		Pattern ePattern = Pattern.compile(regex);
		Matcher eMatcher = ePattern.matcher(line);
		eMatcher.find();
		String cond = line.substring(eMatcher.end(),line.length());
		int parenCount = 1;
		for(int i=0; i<cond.length(); i++) {
			if(cond.charAt(i) == '('){
				parenCount++;
			}
			if(cond.charAt(i) == ')')
				parenCount--;
		}
		while(parenCount >= 1) {
			try {
				line = br.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			cond = cond + line.replaceFirst("(\\s|\t)+", "");
			for(int i=0; i<cond.length(); i++) {
				if(cond.charAt(i) == '('){
					parenCount++;
				}
				if(cond.charAt(i) == ')')
					parenCount--;
			}
		};
		return cond;
	}
}
